package com.yhy.common.swagger.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.google.common.collect.Lists;
import com.yhy.common.swagger.properties.SwaggerProperties;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.OAuthBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Jax-li
 */
@Slf4j
@EnableSwagger2
@EnableKnife4j
@ComponentScan("com.yhy.common.swagger")
@ConditionalOnMissingBean(WebFluxAutoConfiguration.class)
@RequiredArgsConstructor
public class SwaggerConfiguration {

    /**
     * 默认的排除路径，排除Spring Boot默认的错误处理路径和端点
     */
    private static final String REFERENCE = "spring_oauth";
    private final Environment environment;
    private final SwaggerProperties properties;

    @Bean
    public Docket webDocket() {
        log.info("[Swagger Starter] enabled status : {}", properties.getEnabled());
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .build()
                .securitySchemes(Collections.singletonList(securityScheme()))
                .securityContexts(Collections.singletonList(securityContext()))
                .groupName("default")
                .enable(properties.getEnabled());
    }

    /**
     * @return ApiInfo
     */
    private ApiInfo apiInfo() {
        final String appName = environment.getProperty("spring.application.name");
        return new ApiInfoBuilder()
                .title(properties.getInfo().getTitle() + "\t" + appName)
                .contact(properties.getInfo().getContact())
                .description(String.format(properties.getInfo().getDescription(), appName))
                .version(properties.getInfo().getVersion())
                .license(String.format(properties.getInfo().getLicense(), appName))
                .termsOfServiceUrl(String.format(properties.getInfo().getTermsIfServiceUrl(), appName))
                .build();
    }

    /**
     * 使用密码模式
     *
     * @return SecurityScheme
     */
    private SecurityScheme securityScheme() {
        GrantType passwordCredentialsGrant = new ResourceOwnerPasswordCredentialsGrant(properties.getInfo().getAccessTokenUri());
        return new OAuthBuilder()
                .name(REFERENCE)
                .grantTypes(Lists.newArrayList(passwordCredentialsGrant))
                .scopes(Arrays.asList(scopes()))
                .build();
    }

    /**
     * 设置 swagger2 认证的安全上下文
     *
     * @return SecurityContext
     */
    @SuppressWarnings("deprecation")
    private SecurityContext securityContext() {

        return SecurityContext.builder()
                .securityReferences(Collections.singletonList(new SecurityReference(REFERENCE, scopes())))
                .forPaths(PathSelectors.any())
                .build();
    }

    /**
     * 允许认证的scope
     *
     * @return AuthorizationScope[]
     */
    private AuthorizationScope[] scopes() {
        return new AuthorizationScope[]{
                new AuthorizationScope("swagger", "swagger scope is trusted!")
        };
    }

    @Bean
    @SuppressWarnings("all")
    public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {

        return new BeanPostProcessor() {

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                    mySpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }

            private <T extends RequestMappingInfoHandlerMapping> void mySpringfoxHandlerMappings(List<T> mappings) {
                List<T> copy = mappings.stream()
                        .filter(mapping -> mapping.getPatternParser() == null)
                        .collect(Collectors.toList());
                mappings.clear();
                mappings.addAll(copy);
            }

            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    field.setAccessible(true);
                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
    }
}